feat: FlightDeck 1.0.1 package and v1 rollout readiness#1
Conversation
Ship the local-first CLI, schemas, tests, and CI. Slim-repo docs link to canonical flightdeckdev/flightdeck main. OpenTelemetry is optional-only. Also: pytest basetemp under .tmp/pytest for Windows, Python 3.13–3.14 in CI, ruff 0.15.12 aligned with ruff-pre-commit, pre-commit-hooks v5, .gitattributes LF for golden bundle, CHANGELOG 1.0.1 section and empty Unreleased, RELEASE_NOTES v1.0.1 patch notes, README quickstart_smoke first. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
PR SummaryMedium Risk Overview It also tightens release integrity and local-ledger health checks by committing generated Reviewed by Cursor Bugbot for commit 295db21. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Non-atomic audit_seq in
insert_promotion_recordrisks integrityinsert_promotion_recordnow uses the same immediate transaction path as promotion commits, with a regression test covering lock acquisition.
- ✅ Fixed: Duplicate
utc_nowdefined in two modules- Removed the storage-local duplicate and imported the canonical
utc_nowfrommodelsin both storage and CLI usage.
- Removed the storage-local duplicate and imported the canonical
Or push these changes by commenting:
@cursor push 55972ca243
Preview (55972ca243)
diff --git a/src/flightdeck/cli/main.py b/src/flightdeck/cli/main.py
--- a/src/flightdeck/cli/main.py
+++ b/src/flightdeck/cli/main.py
@@ -16,8 +16,17 @@
from flightdeck.config import DEFAULT_CONFIG_FILENAME, load_config, write_default_config
from flightdeck.doctor import run_doctor
from flightdeck.ledger import diff_releases, parse_window
-from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseArtifact, ReleaseRecord, RunEvent
-from flightdeck.storage import Storage, utc_now
+from flightdeck.models import (
+ Policy,
+ PolicyResult,
+ PricingTable,
+ PromotionRecord,
+ ReleaseArtifact,
+ ReleaseRecord,
+ RunEvent,
+ utc_now,
+)
+from flightdeck.storage import Storage
def read_release_artifact(path: Path) -> ReleaseArtifact:
diff --git a/src/flightdeck/storage.py b/src/flightdeck/storage.py
--- a/src/flightdeck/storage.py
+++ b/src/flightdeck/storage.py
@@ -5,18 +5,14 @@
import sqlite3
from contextlib import contextmanager
from dataclasses import dataclass
-from datetime import datetime, timezone
+from datetime import datetime
from pathlib import Path
from typing import Any, Iterable
from uuid import uuid4
-from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent
+from flightdeck.models import Policy, PolicyResult, PricingTable, PromotionRecord, ReleaseRecord, RunEvent, utc_now
-def utc_now() -> datetime:
- return datetime.now(timezone.utc)
-
-
def ensure_parent_dir(db_path: str) -> None:
Path(db_path).expanduser().resolve().parent.mkdir(parents=True, exist_ok=True)
@@ -506,7 +502,7 @@
)
def insert_promotion_record(self, record: PromotionRecord) -> None:
- with self.connect() as conn:
+ with self.transaction() as conn:
self._insert_release_action_conn(conn, record)
def commit_promotion(self, record: PromotionRecord, *, new_promoted_release_id: str) -> None:
diff --git a/tests/test_doctor.py b/tests/test_doctor.py
--- a/tests/test_doctor.py
+++ b/tests/test_doctor.py
@@ -7,6 +7,8 @@
from click.testing import CliRunner
from flightdeck.cli.main import cli
+from flightdeck.models import PolicyResult, PromotionRecord
+from flightdeck.storage import Storage
from tests.test_spine import write_events, write_policy, write_pricing, write_release
@@ -107,6 +109,45 @@
assert "audit_seq" in res.output.lower()
+def test_insert_promotion_record_uses_immediate_transaction(tmp_path: Path) -> None:
+ storage = Storage(str(tmp_path / "flightdeck.db"))
+ storage.migrate()
+ with storage.connect() as conn:
+ conn.execute(
+ """
+ INSERT INTO releases
+ (release_id, agent_id, version, environment, checksum, artifact_json, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ ("rel_1", "agent_support", "1", "local", "sha256:abc", "{}", "2026-05-01T00:00:00+00:00"),
+ )
+
+ record = PromotionRecord(
+ action_id="act_1",
+ action="promote",
+ actor="tester",
+ release_id="rel_1",
+ agent_id="agent_support",
+ environment="local",
+ reason="test",
+ policy_result=PolicyResult(passed=True),
+ created_at=datetime.now(tz=timezone.utc),
+ )
+
+ competing_conn = storage.connect()
+ try:
+ competing_conn.execute("BEGIN IMMEDIATE;")
+ try:
+ storage.insert_promotion_record(record)
+ except sqlite3.OperationalError as exc:
+ assert "database is locked" in str(exc)
+ else:
+ raise AssertionError("insert_promotion_record did not request an immediate write lock")
+ finally:
+ competing_conn.rollback()
+ competing_conn.close()
+
+
def test_doctor_fails_when_promoted_release_missing(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
runner = CliRunner()You can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 295db21. Configure here.
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
…cstrings (#4) - CHANGELOG.md: add Fixed entry under Unreleased for PR #3 (zero policy sample thresholds were silently ignored due to falsy 'or' fallback; now uses 'is not None' checks) - ledger.py: add docstrings to confidence_label, evaluate_policy, and diff_releases explaining threshold semantics (None vs 0 vs config default), confidence label tiers, constraint fields, and agent-id invariant - models.py: add Policy class docstring explaining the None/0 distinction for min_* threshold fields versus constraint (max_*) fields - schemas/v1/policy.schema.json: regenerated to pick up Policy docstring Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>
Implements the #1 amazing-product-gap item — HMAC-signed outbound webhooks for promote/rollback/policy-blocked events. Unblocks Slack, Discord, Teams, PagerDuty, and Linear adoption without FlightDeck owning any of those integrations. New surface (all gated by the existing Bearer / loopback ledger-write policy): - HTTP routes (`src/flightdeck/server/routes/webhooks.py`): - POST /v1/webhooks create (returns the secret once) - GET /v1/webhooks list (secrets redacted to a preview) - DELETE /v1/webhooks/{id} delete - CLI (`flightdeck webhook …`): - `webhook add --url … --event … [--description …]` - `webhook list` (rich table) - `webhook remove WEBHOOK_ID [--yes]` - `webhook test WEBHOOK_ID` (synthetic test.ping payload) Signing follows GitHub convention: - Header: `X-FlightDeck-Signature: sha256=<hex_digest>` - Header: `X-FlightDeck-Event: <event_name>` - Header: `X-FlightDeck-Delivery: <uuid>` - HMAC-SHA256 over the raw request body using the per-webhook secret. Delivery (`src/flightdeck/webhooks.py`): - Synchronous fan-out from the originating promote/rollback handler. - Best-effort: 5 s per-request timeout, 3 attempts, exponential backoff (1 s / 2 s / 4 s), no redirects, TLS verified. - Failures only logged; webhook errors NEVER break a promote/rollback. Event payload (envelope): { "event": "promote.succeeded|rollback.succeeded|promote.blocked", "delivery_id": uuid, "created_at": iso8601, "data": { release_id, agent_id, environment, window, actor, reason, baseline_release_id, action_id, [policy_reasons] } } Schema migration v5 adds the `webhooks` table on both SQLite and PostgreSQL (id, url, events_json, secret, enabled, created_at, description) plus an `idx_webhooks_enabled` index. Storage method contract: insert_webhook / list_webhooks / get_webhook / delete_webhook. Hook points in `operations.py`: - On successful promote / rollback in `_evaluate_promotion_or_rollback`, after `commit_promotion`, fire `promote.succeeded` or `rollback.succeeded` via `_dispatch_webhook_safe` (try/except never re-raises). - On policy-blocked direct promote (action='promote', not policy_result.passed), after `insert_promotion_record`, fire `promote.blocked` with policy_reasons in the payload. The approval workflow path is NOT instrumented to avoid duplicate fires. No new runtime dependencies. `httpx` was already in `pyproject.toml`. Verification: - ruff: clean - pytest --cov-fail-under=80: 167 passed, 4 skipped, 81.68% cov (23 new tests across signing, storage CRUD, route auth + secret redaction, and HTTP delivery with httpx.MockTransport — including retry-on-5xx, give-up-after-3, and event-name filtering) - `flightdeck webhook --help` shows all 4 subcommands - schema drift: clean - static-bundle drift: clean (web/ not touched) Docs: - README: new Webhooks section with a one-paragraph quickstart. - docs/http-api.md: route table for the three webhook endpoints + signature header format. - docs/cli.md: `webhook` subcommand reference. - CHANGELOG: Unreleased > Added bullet for v1.3.0.
…ded-User -> audit actor Closes the #1 rip-out risk from the product audit (/Users/sai.gottam/flightdeck-launch/07a-product-gaps.md): mutations previously logged actor='http' for every HTTP-originated promote / rollback / promote-request / promote-confirm, because the body's Pydantic default was the only signal available to the route handler. New behaviour: the handlers consult headers first. 1. X-FlightDeck-Actor (explicit, for CI wrappers / scripts) 2. X-Forwarded-User (de-facto reverse-proxy / SSO convention -- oauth2-proxy, Pomerium, Authelia, Cloudflare Access, nginx auth_request) 3. body 'actor' (last-resort fallback) The first non-empty, non-whitespace value wins. This lets an upstream auth layer authoritatively stamp the audit ledger without trusting the caller-controlled JSON body, which is the foundation for the eventual SSO / OIDC story without committing to a full identity model today. - src/flightdeck/server/routes/actions.py: new resolve_actor() helper + _ACTOR_HEADERS constant; applied to all four mutating routes. - tests/test_actor_passthrough.py: 7 tests covering precedence, body fallback, whitespace handling, and the documented constant order. - docs/http-api.md: new 'Identity passthrough' subsection under Authentication with the precedence table. Verification on this branch: - ruff: clean - pytest: 174 passed, 4 skipped (was 167; +7 new) -- coverage stays above the 80% floor.
…cs improvements (#62) * docs(deploy): add Fly.io fly.toml and deployment steps Include optional persistent volume mount template, health check on /health, and security notes for FLIGHTDECK_LOCAL_API_TOKEN and read-only UI builds. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * feat(deploy): Railway docs, railway.toml, and PORT-aware entrypoint Railway injects PORT at runtime; entrypoint binds flightdeck serve to PORT with 8765 fallback for local Compose. Dockerfile healthcheck follows PORT. Add railway.toml (Dockerfile builder, /health check) and README section with pricing caveat, volume, and token guidance. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Add flightdeck demo for one-command quickstart onboarding Introduce `flightdeck demo` to run the examples/quickstart ledger flow in a temp workspace without sed or fixture paths. Ship quickstart fixtures in the wheel via Hatch force-include to `_bundled_quickstart` for PyPI installs. Refactor quickstart_smoke to share demo_flow helpers; document FLIGHTDECK_QUICKSTART_ROOT. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * docs: align web-ui, cli, sdk-integrations, and ops-policy with shipped features - web-ui.md: document refactored DiffPage component tree (DiffVerdictStack, DiffReleaseTwin, DiffPolicyPanel, DiffChangeImpact/DiffPricingExpand, DiffDecisionCard, diffPayload.tsx), URL deep-linking for all pages, OverviewPage focused-release hero (?release= param), ReleaseLifecycleStrip, CopyTextButton, urlSearch.ts helpers, updated routing table, new CSS classes for DiffPage and OverviewPage additions - cli.md: add flightdeck pricing check subcommand (--max-age-days, --fail) with example output and CI usage pattern - pricing-catalog.md: link flightdeck pricing check reference to cli.md - operations-and-policy.md: add schema migration v4 (promotion_requests table); update storage schema table from 7 to 8 tables - sdk-integrations.md: add Module reference section documenting make_run_end_event, temporal_labels, and per-integration public APIs (openai_chat, anthropic_messages, openai_agents, langchain_callback, crewai_bridge) Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * docs(repo): add Cursor Cloud specific instructions to AGENTS.md Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Polish web UI for release-quality UX and discoverability Set per-route document titles, sync theme-color with light/dark, add Open Graph meta and clearer sidebar landmark text. Refine controls (touch-friendly buttons/inputs), layered card shadows, and striped data tables. Extend Playwright coverage for titles and theme-color. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Port operator UI primitives from industry-standard polish branch Add Button (loading spinner, aria-busy) and StatusChip components; render API security as scannable Writes/Reads/UI token chips. Wire Diff, Runs, and Actions flows to Button; improve promote reason field validation UX, native-styled filters (fd-select), sticky overview tables, and fd-link on promoted release focus. Rebuild shipped static bundle; align smoke test with chip copy. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Add demo capture script and committed UI marketing artifacts Include Playwright-generated MP4/WebM walkthrough and PNG stills under artifacts/flightdeck-demo-share for easy clone/download; document regeneration via web/scripts/capture-demo-artifacts.mjs. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Address PR review: a11y, validation UX, security strip, and e2e gaps Actions: aria-busy tracks workspace load only; workspace error vs loading copy for token hint; field-level invalid flags for reason and confirm inputs; clear flags on API errors. Runs: mutual disable for load/export; invalid styling when release ID missing. SecurityStatusBar: data-testid, skip /health in read-only UI, unknown auth values with warn copy, chained detail messages; theme-color meta updates all tags. Button: preserve loading aria-busy over rest spread. useDocumentTitle: empty and suffix rules. Demo capture script aligns with e2e CLI resolution and closes browser in finally. Add security-strip e2e (bearer route mock); scope smoke assertion. Rebuild static bundle. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Regenerate demo screenshots and walkthrough video Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Web UI: open Appearance from sidebar Settings popover Replace the dedicated Settings route with a portal dialog next to the footer control, keep theme persistence unchanged, redirect legacy #/settings to home, refresh e2e and demo capture docs, and rebuild the shipped static bundle. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * docs: add GitHub Pages workflow and Ask AI affordances - Add MkDocs Material config, docs landing page, and pinned docs/requirements.txt - Deploy site/ to Pages on main via actions/upload-pages-artifact + deploy-pages - Perplexity/ChatGPT banner, floating Ask AI (Perplexity), extra CSS - Ignore local site/; document preview in DEVELOPMENT and Pages setup in CONTRIBUTING - Link published docs from README and pyproject Documentation URL Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Regenerate FlightDeck UI demo screenshots and walkthrough video Re-run capture-demo-artifacts against current serve UI; refresh README listing for all numbered PNGs plus WebM/MP4 outputs. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * Web UI: icon-only Theme row in sidebar Settings popover Use sun/moon/monitor controls next to a Theme label, rename the dialog to Settings, tighten popover width, and refresh docs, e2e, demo captures, and the shipped static bundle. Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com> * chore(ci): expand Python matrix (3.11/3.12/3.13), add CodeQL + Dependabot - ci.yml: collapse duplicate test/test-windows jobs into single 2D matrix (os × python-version); guard web-build, Playwright e2e, schema-drift to ubuntu+3.12 only to save CI minutes. Add explicit actions/setup-python@v5. - codeql.yml: weekly + per-PR SAST for python and javascript-typescript. - dependabot.yml: weekly version updates for pip, npm (web/), github-actions with minor+patch grouping and a 5-PR cap per ecosystem. Addresses launch-readiness audit gaps (no SAST, no automated dep updates, single Python version tested despite pyproject claiming 3.11-3.14 support). * chore(repo): real maintainer identity; soften Ask AI to floating pill only - pyproject.toml: replace generic 'FlightDeck' author with the real maintainer (Gottam Sai Bharath) and mirror to [maintainers]. Removes the 'looks like abandonware' signal on PyPI/HN. - mkdocs.yml + docs/stylesheets/extra.css: remove the dismissible Ask AI banner (Material extra.announce). The unsolicited outbound to Perplexity/ChatGPT read as a dark pattern on a docs site. - docs/index.md: rewrite Ask AI paragraph to describe the floating pill only. README/CHANGELOG already aligned. * feat(community,supply-chain): industry-standard launch readiness Community files: - CODE_OF_CONDUCT.md: Contributor Covenant 2.1 adaptation with placeholder enforcement contact. - GOVERNANCE.md: honest single-maintainer model with documented path to a steering committee once there are >=3 active maintainers, no single contributor >70% of commits over 6 months, published trademark policy, documented contribution license model. - CITATION.cff: academic-citation metadata (CFF 1.2.0). - README.md: badge row (PyPI version, Python versions, CI, license, GitHub stars, ruff). - .github/ISSUE_TEMPLATE/config.yml: disable blank issues, link to Security Advisories, Discussions, and Documentation. - .github/FUNDING.yml: GitHub Sponsors link for the maintainer. Supply chain & security workflows: - .github/workflows/codeql.yml [previous commit] — already landed. - .github/workflows/scorecard.yml: OpenSSF Scorecard (weekly + branch_protection_rule + manual) -> SARIF -> GitHub Security tab. - .github/workflows/sbom.yml: CycloneDX SBOM on every semver tag, attached as a Release asset. - .github/workflows/trivy.yml: filesystem + container scan (weekly + per-PR), SARIF to Security tab; container scan builds the examples/deploy/ image. - .github/workflows/docker-publish.yml: multi-arch (linux/amd64 + linux/arm64) container image to ghcr.io/flightdeckdev/flightdeck on semver tags, with provenance and SBOM attestations and a 'latest' tag on every release. (Fix: removed enable={{is_default_branch}} from the latest tag rule -- the workflow only runs on tag push, where is_default_branch is always false, so latest would have never been pushed.) - .github/workflows/stale.yml: politely close stale issues/PRs with generous timeouts (60/14 issues, 45/14 PRs) and exempt labels (pinned, security, roadmap, keep-open). These close every gap flagged in the launch-readiness audit (CodeQL, Dependabot, OpenSSF Scorecard, SBOM, container scan, container image distribution, COC, GOVERNANCE, badges, ISSUE_TEMPLATE polish, CFF). * fix(ci): always tag GHCR image as 'latest' on semver tag push docker-publish.yml only triggers on tag push, where is_default_branch is always false; the enable guard meant ':latest' would never be pushed. Drop the guard so every semver release moves ':latest'. * feat(web): apply UI/UX audit P0+P1 fixes P0 (must-fix-before-launch): - ActionsPage: replace window.confirm() with typed-confirm guard (must type last 8 chars of release ID before destructive promote/ rollback proceeds). Same path used for both actions. - ActionsPage: user-settable Actor identity persisted in localStorage (was hardcoded 'react-ui' in every audit row). - DiffPage / ActionsPage: drop hardcoded environment='local' default. Empty inputs disable the Compute / Promote action, with helpful placeholders ('e.g. staging'). - DiffPage: client-validate baseline / candidate inputs (Compute diff disabled while either is empty). - OverviewPage: first-time-user empty-state CTA with the 'flightdeck release register …' command + Quickstart docs link. P1 (strong-before-launch): - DiffPage: swap-baseline-and-candidate button. - OverviewPage: 'Showing N of M' + Clear-filters chip on the filter row. - RunsPage: Next/Previous pagination with returned/total summary. - RunsPage: collapse 5 optional filters (Tenant, Task, Trace ID, Session ID, Span ID) into an Advanced disclosure; Release ID, Window, and Environment stay visible by default. - RunsPage: type='number' + inputMode='numeric' on Offset/Limit. - index.css: drop italic on empty cells (a11y antipattern); fix release-ID mid-character wrap on Diff twin; widen drawer panel to 40rem (was 28rem) for JSON payloads. - AppShell: aria-controls IDREFS compliance (single ID). - ReleaseLifecycleStrip: pre-existing latent TS error fixed — Link → NavLink (Link does not accept the 'end' prop). Deferred for a focused follow-up PR (broke e2e on the first pass and deserve their own scrutiny): - P1-8 React.lazy / Suspense route splitting (HashRouter + Suspense transient mount issue surfaced a strict-mode getByLabel match in the smoke deep-link spec). - P1-11 'Promote (writes ledger)' button copy (smoke spec expects exact 'Promote' button name). Verification on this branch: - npx tsc --noEmit ✅ - npm run build ✅ - npm run test:e2e ✅ (21 passed, 2 skipped) - pytest --cov-fail-under=80 ✅ (144 passed, 4 skipped, 83.01% cov) - ruff ✅ - schema drift ✅ * fix(pr62-review): address all reviewer BLOCKERs + MAJORs + 2 high-leverage MINORs Reviewer findings file: /Users/sai.gottam/flightdeck-launch/08-pr62-review.md Verdict before: READY AFTER FIXING BLOCKERS (2 BLOCKER, 6 MAJOR, 9 MINOR, 4 NIT) BLOCKERs - trivy.yml: pin aquasecurity/trivy-action@master -> @0.28.0 in both jobs. Mutable @master was the lone outlier in the supply-chain workflow stack and would have failed OpenSSF Scorecard's Pinned-Dependencies check (Scorecard was added in the same PR). - CODE_OF_CONDUCT.md: drop the conduct@flightdeck.dev placeholder and the trailing 'maintainers: update this' italic. Enforcement now routes to a GitHub private security advisory (Conduct category) and the maintainer's @Gsbreddy GitHub handle -- both work today, no DNS setup required. MAJORs - ActionsPage.runAction: explicit Release-ID check + non-empty typed-confirm match. Empty rid produced expected='', which an Enter-on-empty prompt satisfied -- the typed-confirm guard looked bypassable on the UI even though the server rejected. Now: '!rid -> Release ID is required', and '!typed' fails the guard. - ActionsPage: actor defaults to '' (was 'react-ui'); the persistence effect only writes non-empty values. The audit-log 'actor=react-ui' problem P0-4 was meant to fix is now fully fixed -- nothing silently pre-populates the audit row. - docker-publish.yml: gate ':latest' on github.event_name == 'push' || the new workflow_dispatch.inputs.push_latest input (default true). Tag push preserves current behavior; backports / hotfixes can opt out so ':latest' never moves backwards. - Dockerfile: pin python:3.14-slim -> python:3.13-slim. CI matrix tests 3.11/3.12/3.13 -- we now actually test the interpreter we ship. - sbom.yml: pin cyclonedx-bom>=4.0 -> ==6.1.4. SBOM tooling itself must be reproducible; the whole point of this workflow is supply-chain provenance. - docs/requirements.txt: add transitive pymdown-extensions==10.16 pin so mkdocs build --strict doesn't break on a silent transitive bump on the first GitHub Pages deploy. MINORs (cheap wins worth bundling) - dependabot.yml: register pip ecosystem for docs/ so the GitHub Pages build gets auto-bumped (mkdocs-material / pymdown-extensions have had CVEs before). - fly.toml: rename app='flightdeck-demo' -> 'REPLACE-ME-your-fly-app-name' with an explanatory comment. The previous placeholder is squatted on Fly.io; users following the docs hit 'name already taken' on fly deploy. Remaining MINOR/NIT findings (intentionally deferred to follow-up PRs): - MINOR-1/2: SHA-pin ossf/scorecard-action and softprops/action-gh-release - MINOR-3/4: docs-site Ask-AI footer note + pages.yml concurrency comment - MINOR-6: document 'web is the only npm root' assumption in dependabot.yml - MINOR-7: CITATION.cff name convention -- needs maintainer confirmation - MINOR-9: post-merge: tag v1.3.0-rc1 to verify the release pipeline still ships a wheel with _bundled_quickstart/ included - NIT-1: PR body file/line counts (cosmetic) - NIT-2: changelog mention of bundled demo artifacts (cosmetic) Verification on this branch: - ruff: clean - pytest --cov-fail-under=80: 144 passed, 4 skipped, 83.01% cov - web tsc: clean - npm run build: clean (74 modules, 322 KB js / 37 KB css) - Playwright e2e: 21 passed, 2 skipped - YAML lint (trivy/sbom/docker-publish/dependabot): all parse cleanly * feat(webhooks): HMAC-signed outbound webhooks (v1.3.0) Implements the #1 amazing-product-gap item — HMAC-signed outbound webhooks for promote/rollback/policy-blocked events. Unblocks Slack, Discord, Teams, PagerDuty, and Linear adoption without FlightDeck owning any of those integrations. New surface (all gated by the existing Bearer / loopback ledger-write policy): - HTTP routes (`src/flightdeck/server/routes/webhooks.py`): - POST /v1/webhooks create (returns the secret once) - GET /v1/webhooks list (secrets redacted to a preview) - DELETE /v1/webhooks/{id} delete - CLI (`flightdeck webhook …`): - `webhook add --url … --event … [--description …]` - `webhook list` (rich table) - `webhook remove WEBHOOK_ID [--yes]` - `webhook test WEBHOOK_ID` (synthetic test.ping payload) Signing follows GitHub convention: - Header: `X-FlightDeck-Signature: sha256=<hex_digest>` - Header: `X-FlightDeck-Event: <event_name>` - Header: `X-FlightDeck-Delivery: <uuid>` - HMAC-SHA256 over the raw request body using the per-webhook secret. Delivery (`src/flightdeck/webhooks.py`): - Synchronous fan-out from the originating promote/rollback handler. - Best-effort: 5 s per-request timeout, 3 attempts, exponential backoff (1 s / 2 s / 4 s), no redirects, TLS verified. - Failures only logged; webhook errors NEVER break a promote/rollback. Event payload (envelope): { "event": "promote.succeeded|rollback.succeeded|promote.blocked", "delivery_id": uuid, "created_at": iso8601, "data": { release_id, agent_id, environment, window, actor, reason, baseline_release_id, action_id, [policy_reasons] } } Schema migration v5 adds the `webhooks` table on both SQLite and PostgreSQL (id, url, events_json, secret, enabled, created_at, description) plus an `idx_webhooks_enabled` index. Storage method contract: insert_webhook / list_webhooks / get_webhook / delete_webhook. Hook points in `operations.py`: - On successful promote / rollback in `_evaluate_promotion_or_rollback`, after `commit_promotion`, fire `promote.succeeded` or `rollback.succeeded` via `_dispatch_webhook_safe` (try/except never re-raises). - On policy-blocked direct promote (action='promote', not policy_result.passed), after `insert_promotion_record`, fire `promote.blocked` with policy_reasons in the payload. The approval workflow path is NOT instrumented to avoid duplicate fires. No new runtime dependencies. `httpx` was already in `pyproject.toml`. Verification: - ruff: clean - pytest --cov-fail-under=80: 167 passed, 4 skipped, 81.68% cov (23 new tests across signing, storage CRUD, route auth + secret redaction, and HTTP delivery with httpx.MockTransport — including retry-on-5xx, give-up-after-3, and event-name filtering) - `flightdeck webhook --help` shows all 4 subcommands - schema drift: clean - static-bundle drift: clean (web/ not touched) Docs: - README: new Webhooks section with a one-paragraph quickstart. - docs/http-api.md: route table for the three webhook endpoints + signature header format. - docs/cli.md: `webhook` subcommand reference. - CHANGELOG: Unreleased > Added bullet for v1.3.0. * feat(server): identity passthrough from X-FlightDeck-Actor / X-Forwarded-User -> audit actor Closes the #1 rip-out risk from the product audit (/Users/sai.gottam/flightdeck-launch/07a-product-gaps.md): mutations previously logged actor='http' for every HTTP-originated promote / rollback / promote-request / promote-confirm, because the body's Pydantic default was the only signal available to the route handler. New behaviour: the handlers consult headers first. 1. X-FlightDeck-Actor (explicit, for CI wrappers / scripts) 2. X-Forwarded-User (de-facto reverse-proxy / SSO convention -- oauth2-proxy, Pomerium, Authelia, Cloudflare Access, nginx auth_request) 3. body 'actor' (last-resort fallback) The first non-empty, non-whitespace value wins. This lets an upstream auth layer authoritatively stamp the audit ledger without trusting the caller-controlled JSON body, which is the foundation for the eventual SSO / OIDC story without committing to a full identity model today. - src/flightdeck/server/routes/actions.py: new resolve_actor() helper + _ACTOR_HEADERS constant; applied to all four mutating routes. - tests/test_actor_passthrough.py: 7 tests covering precedence, body fallback, whitespace handling, and the documented constant order. - docs/http-api.md: new 'Identity passthrough' subsection under Authentication with the precedence table. Verification on this branch: - ruff: clean - pytest: 174 passed, 4 skipped (was 167; +7 new) -- coverage stays above the 80% floor. * feat: request-id middleware, version --json, Slack/Discord webhook recipes Three small additive launch-readiness wins; no overlap with the reviewer's current PR-62 scope. 1. **Request-context middleware (src/flightdeck/server/middleware.py).** Every response now carries two headers: - X-Request-Id: per-request UUID (echoes a caller-supplied value if present, generates a fresh uuid4().hex otherwise). Also stashed on request.state.request_id for downstream handlers / logs / future audit-row enrichment. - X-FlightDeck-Server-Version: package __version__. Lets clients detect server / client skew without hitting /health. Wired in src/flightdeck/server/app.py via app.add_middleware(). Five tests in tests/test_server_middleware.py cover: generated id shape (32 hex chars), client-supplied id echoed, whitespace-only replaced, server version equals __version__, id unique per request. 2. **flightdeck version [--json] CLI command (src/flightdeck/cli/main.py).** Human form (default): 'flightdeck 1.2.0'. Machine form (--json): {"name": "flightdeck-ai", "version": "1.2.0"} — for CI scripts, chatops bots, and dashboards that want a single source of truth. click already exposes --version on the root group; this is the explicit subcommand for cases where --version isn't ergonomic. 3. **Slack / Discord / PagerDuty / Linear webhook recipes (docs/sdk-integrations.md).** New 'Outbound webhooks' section turns the generic JSON envelope into a concrete launch story: Cloudflare Worker example for Slack, one-line variations for Discord and PagerDuty, signature-verification snippet in Python (with the hmac.compare_digest reminder so users don't write timing-vulnerable comparisons). This is the 'webhooks are real' demo a first-time user can copy-paste in 5 minutes. Verification: - ruff: clean - pytest tests/test_server_middleware.py tests/test_actor_passthrough.py tests/test_webhooks_*.py: 35 passed - 'uv run flightdeck version' + 'flightdeck version --json' smoke OK * fix(reviewer-pass-2): SSRF validation on webhook URL, identity-passthrough trust docs, workspace info CLI Addresses both MAJORs from the second reviewer pass (/Users/sai.gottam/flightdeck-launch/08-pr62-review.md "Final pass"): MAJOR-7 — Webhook URL SSRF defence models.py: WebhookCreate.url now runs a validator that rejects - non-http(s) schemes (no file://, gopher://, ftp://, javascript:, data:); - link-local IPv4 and IPv6 literals (covers AWS IMDS 169.254.169.254, ECS 169.254.170.2, and IPv6 fe80::/10); - the canonical cloud-metadata hostnames (metadata.google.internal, metadata, instance-data, instance-data.ec2.internal). Loopback and RFC1918 private addresses are intentionally allowed — FlightDeck is local-first and self-hosted Slack/Discord receivers commonly live on private nets. 21 new tests in tests/test_webhooks_url_validation.py cover both accept and reject paths. MAJOR-8 — Identity-passthrough trust posture SECURITY.md: new "Identity passthrough headers — when to trust them" subsection explicitly warns that X-Forwarded-User and X-FlightDeck-Actor are trivially forgeable without (1) all inbound through a trusted reverse proxy, (2) that proxy stripping any incoming copies of the header before injecting its own value, and (3) Bearer-gating mutating routes. Includes nginx, Caddy, and oauth2-proxy configuration shape. Also added a new "Outbound webhooks — SSRF defence" subsection documenting the validator above. Bonus while the reviewer was running: CLI: new `flightdeck workspace info [--json]` — one-screen snapshot of workspace path + server version + db backend + schema version + ledger counters (releases / promoted / actions / run events) + configuration (default env, policy presence, pricing catalog, promotion-requires-approval) + webhooks count. JSON form is machine-parseable for CI dashboards / chatops. Restores the doctor-command "all passed" summary line that an earlier edit had inadvertently displaced. Verification: - ruff: clean - pytest --cov-fail-under=80: 202 passed, 4 skipped, 82.15% cov (was 174; +28 new tests across SSRF and workspace info) - Doctor smoke: 'all passed' restored - 'flightdeck workspace info' + '--json' both smoke OK * fix(ci): trivy-action tag 0.28.0 doesn't exist; use 0.35.0 (latest non-v stable) The reviewer's suggested pin (0.28.0) referenced a version that aquasecurity/trivy-action never released. Marketplace tag actually exists at 0.35.0 (and v-prefixed v0.36.0). 0.35.0 is the highest tag without the v-prefix variant in the release feed and matches what the action's README documents. * feat(ux+landing): live UI fixes, new screenshots, demo video, README overhaul UX fixes (from live user simulation on running instance): - DiffPage: auto-scroll to policy verdict on Compute diff (useRef + scrollIntoView after 50ms settle) — verdict was below the fold at 1440x900. - DiffPricingExpand: wrap pricing.warnings and pricing.hints in <details><summary> accordions ('Pricing warnings (N)' / 'Pricing hints (N)'), styled fd-muted. Were previously bold orange/blue alert boxes at the same visual weight as policy failures — startling to first-time users. - ActionsPage: improve typed-confirm error — now says 'To confirm, type the last 8 characters of the release ID: "<expected>"' so users know exactly what to type. - RunsPage: Release ID input now has placeholder 'type or paste a release ID' and a hint span 'Type to search known IDs, or paste from Overview' — the <datalist> had no affordance for discoverability. Screenshots (9 screenshots captured with Playwright on the fixed build, 1440x900, against a seeded demo workspace): - docs/screenshots/overview.png — overview with promoted + release tables - docs/screenshots/diff-result.png — policy PASS verdict, auto-scrolled - docs/screenshots/runs.png — runs with datalist hint visible - docs/screenshots/actions.png — actions form pre-filled via deep-link - docs/screenshots/dark-mode.png — dark mode + settings popover - docs/screenshots/flightdeck-demo.webm — 2.5MB full walkthrough recording README overhaul: - 'Try it now' callout (pip install flightdeck-ai + flightdeck demo) promoted to top, before product snapshot. - 'Core loop' → 'How it works' (plain prose, not pipe-separated). - Product snapshot caption updated to link demo + screenshots. - Demo section: clickable overview thumbnail → demo video. - Install section: 'User install' vs 'Contributor install' split. - '~31%' stale claim removed; replaced with accurate description. - Screenshots section: 2×2 gallery using new docs/screenshots/ assets. Cleanup: - Removed artifacts/flightdeck-demo-share/ (old screenshots + MP4/WebM replaced by docs/screenshots/ with better post-fix captures). * fix(pages): enable GitHub Pages, improve docs homepage, add docs badge GitHub Pages: - Enabled via API (Settings → Pages → Source: GitHub Actions). The site will be live at https://flightdeckdev.github.io/flightdeck/ the moment this branch merges to main and the pages.yml workflow runs. Subsequent pushes to main auto-redeploy. - Verified mkdocs build --strict passes locally (all 11 nav pages resolve, no broken internal links). docs/index.md — complete rewrite for first-time visitors: - 'Try it' callout at the top: pip install flightdeck-ai && flightdeck demo (was buried; developer uv install came first). - Plain-English 4-step 'How it works' before the reference table. - 'Who should use this?' section (mirrors README ICP — consistent messaging across landing and docs). - Install section split: user (pip) vs contributor (uv), with extras list (openai, anthropic, postgres, telemetry). - Ask AI pill moved to the bottom — not the first thing a first-time docs visitor should see. README.md: - Added 'Docs' badge linking to https://flightdeckdev.github.io/flightdeck/ in the badge row (between CI and License). * fix(e2e): open pricing-warnings <details> before asserting visibility The P3 UX fix wrapped pricing.hints and pricing.warnings in collapsed <details> accordions (less alarming than bright alert boxes). The diff-ui e2e spec expected the warning text to be immediately visible after clicking the DiffPricingExpand panel, but the item is now inside a closed <details> — hence 'received: hidden'. Add one click on the Pricing-warnings summary before the assertion. * fix(security): bump pymdown-extensions 10.16 -> 10.21.3 Fixes two Trivy CVEs reported on PR #62: - CVE-2026-46338 (MEDIUM) — sibling-prefix path traversal bypass in pymdownx.snippets despite restrict_base_path. Fixed in 10.21.3. - CVE-2025-68142 (LOW) — ReDoS in figure caption extension. Fixed in 10.16.1; 10.21.3 covers both. Verified: mkdocs build --strict still passes with the patched version (0.34s, no errors). * chore(release): bump version 1.2.0 → 1.3.0; finalize CHANGELOG for v1.3.0 Version: - pyproject.toml version = "1.3.0" - src/flightdeck/__init__.py __version__ = "1.3.0" The CHANGELOG already referenced v1.3.0 in the Webhooks entry; this aligns pyproject + __init__ so the release-pypi workflow can tag v1.3.0 immediately after merge without another commit. CHANGELOG additions (all in Unreleased → moved to 1.3.0 on tag): Added: - Identity passthrough (X-FlightDeck-Actor / X-Forwarded-User) - flightdeck workspace info [--json] - flightdeck version [--json] - Request-context middleware (X-Request-Id + X-FlightDeck-Server-Version) - Webhook SSRF URL validation (21 tests) - Web UI UX fixes (diff auto-scroll, typed-confirm, hints disclosure, datalist hint) - Community/supply-chain files (COC, GOVERNANCE, CITATION, FUNDING, ISSUE_TEMPLATE, CodeQL, Scorecard, SBOM, Trivy, GHCR image, Dependabot) - CI matrix 3.11/3.12/3.13 × Linux/Windows Fixed: - pymdown-extensions 10.16 → 10.21.3 (CVE-2026-46338, CVE-2025-68142) Changed: - README overhaul (user install first, demo video, screenshot gallery) --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Gottam Sai Bharath <Gsbreddy@users.noreply.github.com>


Ship the local-first CLI, schemas, tests, and CI. Slim-repo docs link to canonical flightdeckdev/flightdeck main. OpenTelemetry is optional-only.
Also: pytest basetemp under .tmp/pytest for Windows, Python 3.13–3.14 in CI, ruff 0.15.12 aligned with ruff-pre-commit, pre-commit-hooks v5, .gitattributes LF for golden bundle, CHANGELOG 1.0.1 section and empty Unreleased, RELEASE_NOTES v1.0.1 patch notes, README quickstart_smoke first.